package yawning.mew.character;

import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import yawning.mew.equipment.Enchant;
import yawning.mew.equipment.EquipSet;
import yawning.mew.equipment.Item;
import yawning.mew.equipment.ItemSource;
import yawning.mew.equipment.MemoryCacheItemSource;
import yawning.mew.equipment.RawItem;
import yawning.mew.equipment.RawItem.Property;
import yawning.mew.equipment.WowheadItemSource;
import yawning.mew.equipment.XMLItemSource;
import yawning.mew.util.Json;
import yawning.mew.util.URLFetcher;

public class ChardevToonBuilder extends ExternalToonBuilder {
    private int mId;
    private EquipSet mEquipSet;
    private Toon mToon;

    public ChardevToonBuilder() {
    	super();
    }

    public ChardevToonBuilder setId(int id) {
    	mId = id;
    	return this;
    }

    @Override
    public Toon build() throws ToonBuildException {
        try {
            URL url = new URL("http://chardev.org/php/interface/profiles/get_profile.php?id=" + mId);
            String page = URLFetcher.getInstance().fetchAsString(url);
            Json jsa = new Json(page).get("character");

            Toon t = new Toon();

            Json js0 = jsa.get(0);
            t.mRace = Race.get(js0.get(2).get(0).intValue());
            t.mClass = Clazz.get(js0.get(3).get(0).intValue());
            t.level = js0.get(4).intValue();
            t.mTalentSpec = t.mClass.getTalentTrees()[jsa.get(4).intValue()];

            // Get Talents
            int talentNumber = 0;
            for (Json jsitem : jsa.get(2)) {
            	t.mTalents[talentNumber++] = (byte)jsitem.intValue();
            }

            // Get Glyphs
            for (Json jsitem : jsa.get(3)) {
            	t.addGlyph(Glyph.getById(jsitem.get(0).intValue()));
            }

            // Get professions
            for (Json jsitem : jsa.get(5)) {
            	t.addProfession(Profession.get(jsitem.get(0).intValue()), jsitem.get(1).intValue());
            }

            mEquipSet = new EquipSet();
            for (Json jsitem : jsa.get(1)) {
            	if (jsitem == null) continue;

                Item item = new Item();
                try {
                	item.mItem = ItemSource.getItem(jsitem.get(0).get("0").intValue());
                } catch (RuntimeException e) {
                	throw new RuntimeException("Unknown Item: " + jsitem.get(0).get("0").intValue());
                }
                
            	// Get gems
            	for (int g = 1; g <= 3; g++) {
        			Json j = jsitem.get(g);
        			if (j == null) break;
        			try {
        				RawItem gem = ItemSource.getItem(j.get("0").intValue());
            			item.addGem(gem);
        			} catch (RuntimeException e) {
                    	throw new RuntimeException("Unknown Item: " + jsitem.get(0).get("0").intValue());
        			}
            	}

            	// Get enchant.. or sometimes it's a tinker when chardev decides to be retarded
            	Json j = jsitem.get(4);
            	if (j != null) {
            		Enchant enchant = Enchant.getEnchantByEnchantId(j.get(0).intValue());
            		Integer i = enchant.properties.get(Property.PROFESSION_ID); 
            		if (!(i == null) && (i == 202)) {
            			item.addTinker(enchant);
            		}
            		else {
            			item.addEnchant(enchant);
            		}
            		
            	}
            	
            	// Get tinker.. or sometimes it's an enchant when chardev decides to be retarded
            	j = jsitem.get(7);
            	if (j != null) {
            		Iterator<Json> it = j.iterator();
            		if (it.hasNext()) {
                		Enchant enchant = Enchant.getEnchantByEnchantId(it.next().get(0).intValue());
                		Integer i = enchant.properties.get(Property.PROFESSION_ID); 
                		if (!(i == null) && (i == 202)) {
                			item.addTinker(enchant);
                		}
                		else {
                			item.addEnchant(enchant);
                		}
            		}
            	}
            	
            	// Check for items with suffixes and throw an exception.
                if (jsitem.get(0).get("42") != null) {
                	throw new RuntimeException("Random Item Suffix not supported yet");
                }

            	// Reforge
        		int from = jsitem.get(5).get(0).intValue();
        		int to = jsitem.get(5).get(1).intValue();
        		if (from > 0) {
        			item.reforge(sReforge.get(from), sReforge.get(to));
        		}

                if (item.mItem != null) mEquipSet.addItem(item);
            }

            EquipSetToonBuilder builder = new EquipSetToonBuilder()
            		.setBaseToon(t)
            		.setEquipSet(mEquipSet);

            mToon = builder.build();

			// Determine meta gem.
			Item hat = mEquipSet.mEquipmentMap.get(EquipSet.Slot.HEAD);
			if (hat != null) {
				RawItem metaSocket = hat.mSocket1;
				if (metaSocket != null) {
					if (metaSocket.name != null) {
						try {
							// Is it ok to assume that we can sort metagems by prefix?
							mToon.mMetaGem = MetaGem.valueOf(metaSocket.name.split(" ")[0].toUpperCase());
						} catch (IllegalArgumentException e) {
							mToon.mMetaGem = null;
						}
					} else {
						mToon.mMetaGem = MetaGem.get(metaSocket.id);
					}
				}
			}

            return mToon;
        }
        catch (Exception e) {
            throw new ToonBuildException(e);
        }
    }

    @Override
    public EquipSet getEquipSet() {
        return mEquipSet;
    }

    @Override
    public int getCharacterSheetStamina() {
    	if (mToon.mClass == Clazz.WARRIOR && mToon.mTalentSpec == TalentTree.get(Clazz.WARRIOR, "Protection")) {
    		double stamMult = 1.15 * ((mToon.isArmorSpec) ? 1.05: 1.0);

    		return (int) Math.round(mToon.stamina * stamMult);	// Should this be Math.floor? (Off by 1 with http://chardev.org/?profile=186184)
    	}

    	return mToon.stamina;
    }

    @Override
    public int getCharacterSheetParryRating() {
    	if (mToon.mClass == Clazz.WARRIOR) {
    		StatLogic statLogic = StatLogic.get(mToon.level, Clazz.WARRIOR);
    		double strMult = (mToon.mTalentSpec == TalentTree.get(Clazz.WARRIOR, "Protection")) ? 1.0 : 1.05;
    		int strParry = (int) Math.floor((double)(mToon.strength * strMult - statLogic.getBaseStrength(mToon.mRace)) * 0.27);	// FIXME: This may be off for DPS warriors.

    		return mToon.mParryRating + strParry;
    	}

    	return mToon.mParryRating;
    }

    @Override
	public int getCharacterSheetAttackPower() {
    	StatLogic sl = StatLogic.get(mToon.level, mToon.mClass);
    	if (mToon.mClass == Clazz.DRUID) {
    		if (mToon.mTalentSpec == TalentTree.get(Clazz.DRUID, "Feral Combat")) {
    			return (int) Math.round((sl.getBaseAttackPower() + (mToon.strength - 10) * 2 + mToon.attackPower) * 1.25);
    		}
    	} else if (mToon.mClass == Clazz.WARRIOR) {
    		return (int) Math.round(sl.getBaseAttackPower() + (mToon.strength - 10) * 2 + mToon.attackPower);
    	}

    	// Fuck if I know.  Upper layer consumers will complain when the talent spec or class are mismatched so
    	// this does not matter.

    	return mToon.attackPower;
    }

    @Override
    public int getCharacterSheetArmor() {
		double austereMult = mToon.mMetaGem.equals(MetaGem.AUSTERE) ? 1.02 : 1.0;

    	if (mToon.mClass == Clazz.DRUID) {
    		if (mToon.mTalentSpec == TalentTree.get(Clazz.DRUID, "Feral Combat")) {
    			double baseArmorMult = 1.0;
    			int nrThickHide = mToon.getTalent(Talent.getByName(Clazz.DRUID, "Thick Hide"));
    			baseArmorMult += (nrThickHide > 0) ? 0.01 + nrThickHide * 0.03 : 0.0;

    			return (int) Math.round((mToon.armor * baseArmorMult + mToon.bonusArmor) * austereMult);
    		}
    	} else if (mToon.mClass == Clazz.WARRIOR) {
    		double baseArmorMult = 1.0;
    		int nrToughness = mToon.getTalent(Talent.getByName(Clazz.WARRIOR, "Toughness"));
    		baseArmorMult += nrToughness * 0.03 + ((nrToughness == 3) ? 0.01 : 0.0);

    		return (int) Math.round((mToon.armor * baseArmorMult + mToon.bonusArmor) * austereMult);
    	}

    	// This may be off, but if I ever add models for cases where it will be off, I can fix it.

    	return mToon.armor;
    }

    public static void main(String[] args) {
        ItemSource.pushBack(new MemoryCacheItemSource());
        ItemSource.pushBack(new XMLItemSource());
//        ItemSource.pushBack(new ArmoryItemSource());
        ItemSource.pushBack(new WowheadItemSource());

        try {
            ChardevToonBuilder builder = new ChardevToonBuilder().setId(122135);
            Toon toon = builder.build();
            EquipSet equipSet = builder.getEquipSet();

            System.out.println(toon);
            System.out.println(equipSet);

            System.out.println("Character Sheet AP: " + builder.getCharacterSheetAttackPower());
            System.out.println("Character Sheet Armor: " + builder.getCharacterSheetArmor());
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

	private static final Map<Integer,Property> sReforge = new HashMap<Integer,Property>();

	static {
		// var REFORGABLE_STATS = [6,13,14,31,32,36,37,49];
		sReforge.put(6, Property.SPIRIT);
		sReforge.put(13, Property.DODGE_RATING);
		sReforge.put(14, Property.PARRY_RATING);
		sReforge.put(31, Property.HIT_RATING);
		sReforge.put(32, Property.CRIT_RATING);
		sReforge.put(36, Property.HASTE_RATING);
		sReforge.put(37, Property.EXPERTISE_RATING);
		sReforge.put(49, Property.MASTERY_RATING);
	};
}
